package com.immediatus.engine.interpolator;

import com.immediatus.engine.contracts.Interpolator;

import java.util.ArrayList;

public final class SplineInterpolator implements Interpolator{

    private float x1, y1, x2, y2;
    private ArrayList<LengthItem> lengths = new ArrayList<LengthItem>();
    
    public SplineInterpolator(float x1, float y1, float x2, float y2) {
        if (x1 < 0 || x1 > 1.0F ||
            y1 < 0 || y1 > 1.0F ||
            x2 < 0 || x2 > 1.0F ||
            y2 < 0 || y2 > 1.0F) {
            throw new IllegalArgumentException("Control points must be in the range [0, 1]:");
        }
        
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
        
        float prevX = 0.0F;
        float prevY = 0.0F;
        float prevLength = 0.0F;
        for (float t = 0.01F; t <= 1.0F; t += .01F) {
            Point xy = getXY(t);
            float length = prevLength + (float)Math.sqrt((xy._x - prevX) * (xy._x - prevX) + (xy._y - prevY) * (xy._y - prevY));
            LengthItem lengthItem = new LengthItem(length, t);
            lengths.add(lengthItem);
            prevLength = length;
            prevX = xy._x;
            prevY = xy._y;
        }

        for (Object length : lengths){
            LengthItem lengthItem = (LengthItem) length;
            lengthItem.setFraction(prevLength);
        }
    }

    public float interpolate(float fraction_) {
        float interpolatedT = 1.0F;
        float prevT = 0.0F;
        float prevLength = 0.0F;
        for (Object length : lengths){
            LengthItem lengthItem = (LengthItem) length;
            float fraction = lengthItem.getFraction();
            float t = lengthItem.getT();
            if (fraction_ <= fraction){
                float proportion = (fraction_ - prevLength) / (fraction - prevLength);
                interpolatedT = prevT + proportion * (t - prevT);
                return getY(interpolatedT);
            }
            prevLength = fraction;
            prevT = t;
        }
        return getY(interpolatedT);
    }

    private Point getXY(float t) {
        Point xy;
        float invT = (1 - t);
        float b1 = 3 * t * (invT * invT);
        float b2 = 3 * (t * t) * invT;
        float b3 = t * t * t;
        xy = new Point((b1 * x1) + (b2 * x2) + b3, (b1 * y1) + (b2 * y2) + b3);
        return xy;
    }
    
    private float getY(float t) {
        float invT = (1 - t);
        float b1 = 3 * t * (invT * invT);
        float b2 = 3 * (t * t) * invT;
        float b3 = t * t * t;
        return (b1 * y1) + (b2 * y2) + b3;
    }

    class LengthItem {
        float _length;
        float _t;
        float _fraction;

        LengthItem(float length, float t, float fraction) {
            this._length = length;
            this._t = t;
            this._fraction = fraction;
        }

        LengthItem(float length, float t) {
            this._length = length;
            this._t = t;
        }

        public float getLength() {
            return _length;
        }

        public float getT() {
            return _t;
        }

        public float getFraction() {
            return _fraction;
        }

        void setFraction(float totalLength) {
            _fraction = _length / totalLength;
        }
    }

    class Point {
        float _x;
        float _y;

        Point(float x_, float y_){
            _x = x_;
            _y = y_;
        }

        public float getX(){
            return _x;
        }

        public float getY(){
            return _y;
        }
    }
}